package com.tarry.word;

import com.tarry.common.exception.MergeWordException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.Document;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFPictureData;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.*;

/**
 * word 合并工具类
 */
public class MergerWordUtils {
    private MergerWordUtils() {
    }

    /**
     * @param prefix
     * @return
     */
    private static String distinctXmlns(String prefix) {
        int start = prefix.indexOf("xmlns");
        int end = prefix.indexOf("xmlns", start + 1);
        Set s = new HashSet();
        while (end > 0) {
            s.add(prefix.substring(start, end));
            start = end;
            end = prefix.indexOf("xmlns", start + 1);
        }
        String xmlHead = "<xml-fragment ";
        StringBuffer sb = new StringBuffer(xmlHead);
        Map<String, String> map = distinctXmlns(s);
        for (Map.Entry<String, String> entry : map.entrySet()) {
            sb.append(" ");
            sb.append(entry.getKey());
            sb.append("=");
            sb.append(entry.getValue());
        }
        sb.append(">");
        return sb.toString();
    }

    /**
     * xmlns 可能存在xmlns头相同但是指向地址不同的情况
     *
     * @param set
     * @return
     */
    private static Map<String, String> distinctXmlns(Set set) {
        Map<String, String> map = new HashMap();
        Iterator i = set.iterator();
        while (i.hasNext()) {
            String xmlns = (String) i.next();
            map.put(xmlns.substring(0, xmlns.indexOf("=")), xmlns.substring(xmlns.indexOf("=") + 1));
        }
        return map;
    }

    /**
     * @param path
     * @return
     * @throws Exception
     */
    public static XWPFDocument getDocument(String path) throws Exception{
        if (StringUtils.isBlank(path)) {
            throw new MergeWordException("The path can not be empty");
        }
        OPCPackage opcPackage = OPCPackage.open(path);
        return new XWPFDocument(opcPackage);
    }

    /**
     * 合并word 将word有序合并，从第一个往后拼接，每个word进行分页
     * @param path 源word地址
     * @param outPath 合并后word地址
     * @throws Exception
     */
    public static void mergeWord(List<String> path,String outPath) throws Exception{
        if (CollectionUtils.isEmpty(path)) {
            throw new MergeWordException("The path can not be empty");
        }
        if(path.size() < 2){
            throw new MergeWordException("The number of files must exceed 2");
        }
        XWPFDocument src = appendBody(path);

        // 需要配置导出文件路径 记得替换为自己电脑的路径
        OutputStream dest = new FileOutputStream(outPath);
        src.write(dest);
    }

    /**
     * 拼接所有的文档元素
     * @param wordPath
     * @throws Exception
     */
    private static XWPFDocument appendBody(List<String> wordPath) throws Exception {
        // 所有的xmlns
        StringBuffer allXmlns = new StringBuffer();
        // 所有文档的内部元素
        StringBuffer allElement = new StringBuffer();
        XWPFDocument src = getDocument(wordPath.get(0));
        String xmlText = src.getDocument().xmlText();
        allXmlns.append(xmlText, xmlText.indexOf("xmlns"), xmlText.indexOf(">"));
        allElement.append(xmlText, xmlText.indexOf(">") + 1, xmlText.lastIndexOf("</"));
        Map<String,String> map = null;
        for (int i = 1; i < wordPath.size(); i++) {
            if(null != map){
                map.clear();
            }
            XWPFDocument append = getDocument(wordPath.get(i));
            if(wordPath.size() - 1 < i){
                XWPFParagraph paragraph = append.createParagraph();
                paragraph.setPageBreak(true);
            }
            map = getMap(src,append);
            String appendString = append.getDocument().xmlText();
            allXmlns.append(appendString, appendString.indexOf("xmlns"), appendString.indexOf(">"));
            String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
            addPart = replaceContent(addPart, map);
            allElement.append(addPart);
        }
        // 将xmlns去重
        String distinctPrefix = distinctXmlns(allXmlns.toString());
        // 合并文档
        CTBody makeBody = CTBody.Factory.parse(distinctPrefix + allElement.toString() + "</xml-fragment>");
        src.getDocument().getBody().set(makeBody);
        return src;
    }

    /**
     * 对xml字符串中图片ID进行替换
     * @param xmlText
     * @param pictureMap
     * @return
     */
    private static String replaceContent(String xmlText,Map<String,String> pictureMap){
        if (pictureMap != null && !pictureMap.isEmpty()) {
            //对xml字符串中图片ID进行替换
            for (Map.Entry<String, String> set : pictureMap.entrySet()) {
                xmlText = xmlText.replace(set.getKey(), set.getValue());
            }
        }
        return xmlText;
    }

    /**
     *  获取word文档中拼接文档的图片新ID
     * @param src
     * @param append
     * @return
     * @throws Exception
     */
    private static Map<String, String> getMap(XWPFDocument src, XWPFDocument append) throws Exception {
        List<XWPFPictureData> allPictures = append.getAllPictures();
        // 记录图片合并前及合并后的ID
        Map<String, String> map = new HashMap();
        for (XWPFPictureData picture : allPictures) {
            String before = append.getRelationId(picture);
            //将原文档中的图片加入到目标文档中
            String after = src.addPictureData(picture.getData(), Document.PICTURE_TYPE_PNG);
            map.put(before, after);
        }
        return map;
    }
}

